/*
 * Copyright (c) 2013-2015 Charkey. All rights reserved.
 *
 * This software is the confidential and proprietary information of Charkey.
 * You shall not disclose such Confidential Information and shall use it only
 * in accordance with the terms of the agreements you entered into with Charkey.
 *
 * Charkey MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE SOFTWARE,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
 *
 * Charkey SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING,
 * MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
 */

package cn.simastudio.talos.core.controller;

import cn.simastudio.talos.common.constants.ApiStatusCode;
import cn.simastudio.talos.common.constants.SimaConstants;
import cn.simastudio.talos.core.service.base.CoreToInterService;
import cn.simastudio.talos.core.service.base.OAuthService;
import org.apache.oltu.oauth2.as.issuer.MD5Generator;
import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl;
import org.apache.oltu.oauth2.as.request.OAuthTokenRequest;
import org.apache.oltu.oauth2.as.response.OAuthASResponse;
import org.apache.oltu.oauth2.common.OAuth;
import org.apache.oltu.oauth2.common.error.OAuthError;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.OAuthResponse;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import org.apache.oltu.oauth2.common.message.types.TokenType;
import org.apache.oltu.oauth2.common.utils.OAuthUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URISyntaxException;

/**
 * 访问令牌控制器
 * 1、首先通过如http://localhost:8080/chapter17-server/accessToken，POST提交如下数据：
 * client_id=c1ebe466-1cdc-4bd3-ab69-77c3561b9dee
 * &client_secret=d8346ea2-6017-43ed-ad68-19c0f971738b
 * &grant_type=authorization_code&code=828beda907066d058584f37bcfd597b6
 * &redirect_uri=http://localhost:9080/chapter17-client/oauth2-login访问；
 * 2、该控制器会验证client_id、client_secret、auth code的正确性，如果错误会返回相应的错误；
 * 3、如果验证通过会生成并返回相应的访问令牌access token。
 */
@RestController
public class AccessTokenController {

    private static final Logger LOGGER = LoggerFactory.getLogger(AccessTokenController.class);

    @Autowired
    private OAuthService oAuthService;

    @Autowired
    private CoreToInterService coreToInterService;

    @SuppressWarnings("unchecked")
    @RequestMapping(value = "/oauth2/token", method = RequestMethod.POST)
    public HttpEntity token(HttpServletRequest request) throws URISyntaxException, OAuthSystemException {
        try {
            //构建 OAuth 请求
            OAuthTokenRequest oauthRequest = new OAuthTokenRequest(request);

            // client_id
            String clientId = oauthRequest.getClientId();
            // client_secret
            String clientSecret = oauthRequest.getClientSecret();
            // grant_type
            String grantType = oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE);

            //检查提交的 client_id 是否正确
            if (!oAuthService.checkClientId(clientId)) {
                LOGGER.debug("invalid client_id");
                OAuthResponse response =
                        OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                                .setError(OAuthError.TokenResponse.INVALID_CLIENT)
                                .setErrorDescription("invalid client_id/client_secret")
                                .setParam(SimaConstants.Response.CODE, ApiStatusCode.INVALID_CLIENT)
                                .buildJSONMessage();
                return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
            }

            // 检查提交的 client_secret 是否正确
            if (!oAuthService.checkClientSecret(clientSecret)) {
                LOGGER.debug("invalid client_secret");
                OAuthResponse response =
                        OAuthASResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
                                .setError(OAuthError.TokenResponse.INVALID_CLIENT)
                                .setErrorDescription("invalid client_id/client_secret")
                                .setParam(SimaConstants.Response.CODE, ApiStatusCode.INVALID_CLIENT)
                                .buildJSONMessage();
                return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
            }

            // 到此校验完成

            // 用于生成 access_token、refresh_token
            OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());

            // 验证类型：authorization_code，通过 authorization_code 换取 access_token
            if (grantType.equals(GrantType.AUTHORIZATION_CODE.toString())) {
                LOGGER.debug("GrantType: AUTHORIZATION_CODE");
                // 获取 auth_code
                String authCode = oauthRequest.getParam(OAuth.OAUTH_CODE);
                // authorization_code，用于在使用此auth_code获取access_token之后，即可失效auth_code；

                //检查提交的授权码 auth_code 是否正确
                if (!oAuthService.checkAuthCode(authCode)) {
                    LOGGER.debug("invalid authorization_code");
                    OAuthResponse response = OAuthASResponse
                            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                            .setError(OAuthError.ResourceResponse.INVALID_TOKEN)
                            .setErrorDescription("invalid authorization code")
                            .setParam(SimaConstants.Response.CODE, ApiStatusCode.INVALID_AUTH_CODE)
                            .buildJSONMessage();
                    return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
                }

                // auth_code 校验通过

                // 通过 auth_code 在 auth_code_cache 查找 cache，key 为 auth_code，value 为 credential
                String credential = oAuthService.getCredentialByAuthCode(authCode);

                // 生成 access_token
                final String accessToken = oauthIssuerImpl.accessToken();
                // 将 access_token 放到 access_token_cache 中，过期时间 1周
                oAuthService.addAccessToken(accessToken, credential);

                //生成 refresh_token
                final String refreshToken = oauthIssuerImpl.refreshToken();
                // 将 refresh_token 放到 refresh_token_cache 中，过期时间 30天，手动维护 refresh_token
                oAuthService.addRefreshToken(refreshToken, new String[]{credential, accessToken});

                // 失效 auth_code
                oAuthService.evictAuthCode(authCode);

                // 生成 OAuth 响应
                // Sima：通过 coreToInterService 调用Inter模块的service类，获得会员ID；之后的集成需要注意修改此处!!!
                OAuthResponse response = OAuthASResponse
                        .tokenResponse(HttpServletResponse.SC_OK)
                        .setTokenType(TokenType.BEARER.toString())
                        .setAccessToken(accessToken)
                        .setRefreshToken(refreshToken)
                        .setExpiresIn(String.valueOf(oAuthService.getAccessTokenExpireIn()))
                        .setParam("member_id", coreToInterService.getMemberIdByTel(credential).toString())
                        .buildJSONMessage();

                //HTTP头信息中明确指定不得缓存
                HttpHeaders headers = new HttpHeaders();
                headers.add("Cache-Control", "no-store");
                headers.add("Pragma", "no-cache");

                //根据OAuthResponse生成ResponseEntity
                return new ResponseEntity(response.getBody(), headers, HttpStatus.valueOf(response.getResponseStatus()));
            } else if (grantType.equals(GrantType.REFRESH_TOKEN.toString())) { // 验证类型：refresh_token，通过refresh_token换取access_token

                // 获取 refresh_token
                String refreshToken = oauthRequest.getParam(OAuth.OAUTH_REFRESH_TOKEN);
                //检查是否提交了 refresh_token
                if (!oAuthService.checkRefreshToken(refreshToken)) {
                    LOGGER.debug("invalid refresh_token");
                    OAuthResponse response = OAuthASResponse
                            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                            .setError(OAuthError.TokenResponse.INVALID_REQUEST)
                            .setErrorDescription("invalid refresh_token")
                            .setParam(SimaConstants.Response.CODE, ApiStatusCode.INVALID_REFRESH_TOKEN)
                            .buildJSONMessage();
                    return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
                }

                // 通过 refresh_token 获取 access_token，用于判断 access_token 是否确实已经过期
                String accessToken = oAuthService.getAccessTokenByRefreshToken(refreshToken);

                // 检查 access_token 是否过期：refresh_token 只有在 access_token 过期时才能使用，并且只能使用一次
                if (!oAuthService.checkAccessTokenExpired(accessToken)) {
                    LOGGER.debug("access token still valid");
                    OAuthResponse response = OAuthASResponse
                            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                            .setError(OAuthError.TokenResponse.INVALID_REQUEST)
                            .setErrorDescription("access token still valid")
                            .setParam(SimaConstants.Response.CODE, ApiStatusCode.VALID_ACCESS_TOKEN)
                            .buildJSONMessage();
                    return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
                }

                // access_token 已过期，可以使用 refresh_token 换取新的 access_token 和 refresh_token

                // 获取 credential
                String credential = oAuthService.getCredentialByRefreshToken(refreshToken);

                // 生成 新的 access_token
                final String newAccessToken = oauthIssuerImpl.accessToken();
                // 将 access_token 放到 access_token_cache 中，过期时间 1周
                oAuthService.addAccessToken(newAccessToken, credential);

                //生成 新的 refresh_token
                final String newRefreshToken = oauthIssuerImpl.refreshToken();
                // 将 refresh_token 放到 refresh_token_cache 中，过期时间 30天，手动维护 refresh_token
                oAuthService.addRefreshToken(newRefreshToken, new String[]{credential, newAccessToken});

                // 失效之前的 refresh_token
                oAuthService.evictRefreshToken(refreshToken);

                //生成OAuth响应
                OAuthResponse response = OAuthASResponse
                        .tokenResponse(HttpServletResponse.SC_OK)
                        .setTokenType(TokenType.BEARER.toString())
                        .setAccessToken(newAccessToken)
                        .setRefreshToken(newRefreshToken)
                        .setExpiresIn(String.valueOf(oAuthService.getAccessTokenExpireIn()))
                        .setParam("member_id", coreToInterService.getMemberIdByTel(credential).toString())
                        .buildJSONMessage();

                //HTTP头信息中明确指定不得缓存
                HttpHeaders headers = new HttpHeaders();
                headers.add("Cache-Control", "no-store");
                headers.add("Pragma", "no-cache");

                //根据OAuthResponse生成ResponseEntity
                return new ResponseEntity(response.getBody(), headers, HttpStatus.valueOf(response.getResponseStatus()));

            } else { // 其他错误的请求
                LOGGER.debug("invalid request, other error occurred");
                OAuthResponse response = OAuthASResponse
                        .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                        .setError(OAuthError.TokenResponse.INVALID_REQUEST)
                        .setErrorDescription("invalid request, other error occurred")
                        .setParam(SimaConstants.Response.CODE, ApiStatusCode.BAD_REQUEST)
                        .buildJSONMessage();
                return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
            }

        } catch (OAuthProblemException e) {
            LOGGER.debug("OAuthProblemException, error occurred!");
            // 以下开始判断两个操作都有的参数
            // 错误：client_id 为空
            if (OAuthUtils.isEmpty(request.getParameter(OAuth.OAUTH_CLIENT_ID))) {
                OAuthResponse response =
                        OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                                .setError(OAuthError.TokenResponse.INVALID_REQUEST)
                                .setErrorDescription("Missing parameter, parameter name: client_id")
                                .setParam(SimaConstants.Response.CODE, ApiStatusCode.INVALID_CLIENT)
                                .buildJSONMessage();
                return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
            }
            // 错误：client_secret 为空
            if (OAuthUtils.isEmpty(request.getParameter(OAuth.OAUTH_CLIENT_SECRET))) {
                OAuthResponse response =
                        OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                                .setError(OAuthError.TokenResponse.INVALID_REQUEST)
                                .setErrorDescription("Missing parameter, parameter name: client_secret")
                                .setParam(SimaConstants.Response.CODE, ApiStatusCode.INVALID_CLIENT)
                                .buildJSONMessage();
                return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
            }
            // 错误：grant_type 为空
            if (OAuthUtils.isEmpty(request.getParameter(OAuth.OAUTH_GRANT_TYPE))) {
                OAuthResponse response =
                        OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                                .setError(OAuthError.TokenResponse.INVALID_REQUEST)
                                .setErrorDescription("Missing parameter, parameter name: grant_type")
                                .setParam(SimaConstants.Response.CODE, ApiStatusCode.INVALID_GRANT_TYPE)
                                .buildJSONMessage();
                return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
            }

            String grantType = request.getParameter(OAuth.OAUTH_GRANT_TYPE);
            // 下面判断通过 auth_code 获取 access_token 场景的参数
            if (grantType.equals(GrantType.AUTHORIZATION_CODE.toString())) {
                // 错误：auth_code/code 为空
                if (OAuthUtils.isEmpty(request.getParameter(OAuth.OAUTH_CODE))) {
                    OAuthResponse response =
                            OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                                    .setError(OAuthError.TokenResponse.INVALID_REQUEST)
                                    .setErrorDescription("Missing parameter, parameter name: code")
                                    .setParam(SimaConstants.Response.CODE, ApiStatusCode.INVALID_AUTH_CODE)
                                    .buildJSONMessage();
                    return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
                }
                // 错误：redirect_uri 为空
                if (OAuthUtils.isEmpty(request.getParameter(OAuth.OAUTH_REDIRECT_URI))) {
                    OAuthResponse response =
                            OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                                    .setError(OAuthError.TokenResponse.INVALID_REQUEST)
                                    .setErrorDescription("Missing parameter, parameter name: redirect_uri")
                                    .setParam(SimaConstants.Response.CODE, ApiStatusCode.INVALID_CALLBACK_URL)
                                    .buildJSONMessage();
                    return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
                }
            } else if (grantType.equals(GrantType.REFRESH_TOKEN.toString())) {
                // 错误：refresh_token 为空
                if (OAuthUtils.isEmpty(request.getParameter(OAuth.OAUTH_REFRESH_TOKEN))) {
                    OAuthResponse response =
                            OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                                    .setError(OAuthError.TokenResponse.INVALID_REQUEST)
                                    .setErrorDescription("Missing parameter, parameter name: refresh_token")
                                    .setParam(SimaConstants.Response.CODE, ApiStatusCode.INVALID_REFRESH_TOKEN)
                                    .buildJSONMessage();
                    return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
                }
            }
            OAuthResponse res = OAuthASResponse
                    .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                    .error(e)
                    .setParam(SimaConstants.Response.CODE, ApiStatusCode.BAD_REQUEST)
                    .buildJSONMessage();
            return new ResponseEntity(res.getBody(), HttpStatus.valueOf(res.getResponseStatus()));
        }
    }

}
